package nz.ac.massey.whatiscomputerscience.implementation.model;

import java.util.Collection;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;

import nz.ac.massey.whatiscomputerscience.core.model.Model;
import nz.ac.massey.whatiscomputerscience.core.model.VectorImage;
import nz.ac.massey.whatiscomputerscience.core.model.Polygon;
import nz.ac.massey.whatiscomputerscience.core.model.animator.Animator;


/**
 * The NiftyModel is a threaded implementation of the abstract Model
 * 
 * @author Francis Stephens
 */
public class NiftyModel extends Model implements Runnable
{
	private boolean isRunning;
	private Object stableImageLock = new Object( ); // This lock locks access to the stableImage while it is being updated
	private Object AnimationLock = new Object( );  // This lock locks access to the Animations while animation is taking place
	//The animationTimer introduces avoidable thread overhead - shouldn't be too hard to hard code it into this class
	private Timer animationTimer;
	private RunAnimationTask animationTask;
	private Collection<Animator> animators;
	private Animator currentAnimator;
	private Collection<Polygon> stableImageDescription;
	private VectorImage vectorImage;
	private SchedulingPolicy myPolicy = SchedulingPolicy.SINGULAR;
	
	// ANIMATION_RATE is currently a constant but can easily be made publicly modifiable
	private static final long ANIMATION_RATE = 30; // This determines the delay in milliseconds between invocations of the animation step

	/**
	 * @param image The image for this model
	 */
	public NiftyModel( VectorImage image )
	{
		this.vectorImage = image;
		this.animationTask = new RunAnimationTask( this );
	}
	
	/**
	 * @see nz.ac.massey.whatiscomputerscience.core.model.Model#terminate( )
	 */
	@Override
	public void terminate( )
	{
		this.isRunning = false;
	}

	/**
	 * @see nz.ac.massey.whatiscomputerscience.core.model.animator.Animatable#acceptAnimator( nz.ac.massey.whatiscomputerscience.core.model.animator.Animator )
	 */
	public void acceptAnimator( Animator animator )
	{
		if( this.willAccept( animator ) )
		{
			synchronized( this.AnimationLock )
			{
				animator.setVectorImage( this.vectorImage );
				this.animators.add( animator );
				this.notify( );
			}
		}
	}

	/**
	 * @see nz.ac.massey.whatiscomputerscience.core.model.animator.Animatable#willAccept( nz.ac.massey.whatiscomputerscience.core.model.animator.Animator )
	 */
	public boolean willAccept( @SuppressWarnings("unused")Animator animator )
	{
		// If we were going to get really fancy we should have seperate acceptance policy objects for this - bit too much at this stage
		switch( myPolicy )
		{
			case SINGULAR : return  this.animators.isEmpty( );
			case SEQUENTIAL : return true;
			case SIMULTANEOUS : return true;
		}
		
		return false;
	}
	
	/**
	 * @see nz.ac.massey.whatiscomputerscience.core.model.animator.Animatable#animationsPending( )
	 */
	public boolean animationsPending( )
	{
		synchronized( this.AnimationLock )
		{
			return !this.animators.isEmpty( );
		}
	}

	/**
	 * @see nz.ac.massey.whatiscomputerscience.core.model.Model#getViewableState( )
	 */
	@Override
	public Collection<Polygon> getViewableState( )
	{
		synchronized( this.stableImageLock )
		{
			return this.stableImageDescription;
		}
	}

	/**
	 * A running Model thread is receptive to method calls from both the Controller and all registered Views
	 * Models with no animators active or pending will wait
	 * 
	 * Successfully providing an animation to a sleeping Model will wake it
	 * 
	 * @see java.lang.Runnable#run( )
	 */
	public void run( )
	{
		while( isRunning )
		{
			if( animators.isEmpty( ) )
			{
				try
				{
					// I think this will throw an IllegalMonitorStateException.
					// This is because this method is not synchronized
					// TODO May have to rethink this
					this.wait( );
				}
				catch( InterruptedException e )
				{
					// This model has been interrupted and will self terminate
					this.terminate( );
				}
			}
			else // There are animators.  Run them.
			{
				animationTimer.scheduleAtFixedRate( this.animationTask, 0, ANIMATION_RATE );
			}
		}
	}

	/**
	 * Executes one animation step for this model
	 * This method's behaviour is affected chiefly by the scheduling policy of the Model
	 * NB: There is a complex relationship between this method and the acceptAnimator( ) method
	 * Try not to lock acceptAnimator( ) out while this is running - lol don't sleep( ) inside synchronized blocks, use wait( ) instead
	 * TODO ??? An alternative to this would be to make the Animator Objects all instances of TimerTask and just let them loose - that is a great many threads however
	 * 
	 * This method is protected because it is used by the inner class RunAnimationTask.  This is purely for performance.
	 */
	protected void animationStep( )
	{
		synchronized( this.AnimationLock )
		{
			switch( myPolicy )
			{
				case SINGULAR : simultaneousAnimationStep( ); break;
				case SEQUENTIAL : sequentialAnimationStep( ); break;
				case SIMULTANEOUS : simultaneousAnimationStep( ); break;
			}
		}
		
		synchronized( this.stableImageLock )
		{
			this.updateStableImage( );
		}
		
		// All views must be notified of the available stable image
		this.notifyObservers( );
	}

	/*
	 * NB: This method must be covered by an AnimationLock synchronization in the invocation stack
	 */
	private void sequentialAnimationStep( )
	{
		Iterator<Animator> itr;
		
		if( this.animationsPending( ) )
		{
			return;
		}
		
		if( this.currentAnimator == null )
		{
			itr = this.animators.iterator( );
			this.currentAnimator = itr.next( );
		}
		
		finalAnimationStep( );
	}

	/*
	 * This method invokes the mutate( ) method of each available Animator exactly once
	 * If an Animator is stopped it will be removed from animators
	 * 
	 * NB: This method must be covered by an AnimationLock synchronization in the invocation stack
	 */
	private void simultaneousAnimationStep( )
	{
		Iterator<Animator> itr;
		
		itr = this.animators.iterator( );
		
		while( itr.hasNext( ) )
		{
			currentAnimator = itr.next( );
			
			finalAnimationStep( );
		}
	}

	/*
	 * NB: This method must be covered by an AnimationLock synchronization in the invocation stack
	 */
	private void finalAnimationStep( )
	{
		currentAnimator.mutate( );
		
		if( currentAnimator.isStopped( ) )
		{
			animators.remove( currentAnimator );
		}
	}

	/*
	 * This method creates an immutable copy of the current vectorImage for reading by views
	 * NB: This method must be covered by an stableImageLock synchronization in the invocation stack
	 */
	private void updateStableImage( )
	{
		this.stableImageDescription = this.vectorImage.getImageDescription( );
	}
	
	/*
	 * This class enables the use of Timer Objects to run the animators for their Models
	 */
	private class RunAnimationTask extends TimerTask
	{
		NiftyModel myModel;
		
		RunAnimationTask( NiftyModel myModel )
		{
			this.myModel = myModel;
		}
		
		/**
		 * @see java.lang.Runnable#run( )
		 */
		@Override
		public void run( )
		{
			while( myModel.animationsPending( ) )
			{
				myModel.animationStep( );
			}
		}
	}
}